/*
 * Copyright 2007 Xplayat, S.L. All rights reserved.
 * Propiedad de Xplayat. 
 */
package es.xpt.ysf.jaas;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.acl.Group;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.jacc.PolicyContext;
import javax.security.jacc.PolicyContextException;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import javax.transaction.Transaction;

import org.apache.commons.codec.binary.Base64;
import org.jboss.security.SimpleGroup;
import org.jboss.security.auth.spi.UsernamePasswordLoginModule;
import org.jboss.tm.TransactionDemarcationSupport;

import es.xpt.ysf.configuration.Configuration;
import es.xpt.ysf.configuration.ConfigurationParameterNames;
import es.xpt.ysf.logging.LogManager;
import es.xpt.ysf.view.model.UserBean;
import es.xpt.ysf.web.Constants;

  /**
   * <p>
   * RdbmsLoginModule is a LoginModule that authenticates
   * a given username/password credential against a JDBC
   * datasource.
   *
   * @see     javax.security.auth.spi.LoginModule
   * @author  Nacho Estrada
   * @version 1.0
   */

public class YSFLoginModule  extends UsernamePasswordLoginModule { // NO_UCD
	
	/** The JNDI name of the DataSource to use */
	
	private String dsJndiName = null;
	private String bigDataJndiName = null;
	
   /** The sql query to obtain the user password */
   private String principalsQuery = null;;
   /** The sql query to obtain the user roles */
   private String rolesQuery = "select role, 'Roles' from ysf_jaas_principal_role where principal=?";
   /** Whether to suspend resume transactions during database operations */
   private boolean suspendResume = true;
   
   private String excludedEnvironments = null;
   
   private String company = null;
   private String description = null;
   private String entityId = null;
   private String application = null;
   private Locale defaultLocale = null;
   protected String myUsername = null;
   private String email = null;
   
   private Group[] roleSets;
	   
	   /**
	    * Initialize this LoginModule.
	    * 
	    * @param options -
	    * dsJndiName: The name of the DataSource of the database containing the
	    *    Principals, Roles tables
	    * principalsQuery: The prepared statement query, equivalent to:
	    *    "select Password from Principals where PrincipalID=?"
	    * rolesQuery: The prepared statement query, equivalent to:
	    *    "select Role, RoleGroup from Roles where PrincipalID=?"
	    */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public void initialize(Subject subject, CallbackHandler callbackHandler,
	      Map sharedState, Map options)
	   {
	      super.initialize(subject, callbackHandler, sharedState, options);
	      
	      excludedEnvironments = (String) options.get("excludedEnvironments");
	      principalsQuery = (String) options.get("principalsQuery");
	      
	      if (log.isTraceEnabled())
	      {
	         log.trace("DatabaseServerLoginModule, dsJndiName="+dsJndiName);
	         log.trace("principalsQuery="+principalsQuery);
	         log.trace("rolesQuery="+rolesQuery);
	         log.trace("excludedEnvironments="+excludedEnvironments);
	         log.trace("suspendResume="+suspendResume);
	      }
	   }

	@Override
	protected Principal createIdentity(String username) {
		YSFPrincipal p = new YSFPrincipal(username);
		int idx = username.lastIndexOf("@");
		if ( idx!=-1) {
			myUsername= username.substring(0, idx);
			application = username.substring(idx+1);
		}
		p.setUserCode(myUsername);
		p.setCompany(company);
		p.setEmail(email);
		p.setDescription(description);
		p.setEntity(entityId);		
		p.setApplication(application);
		p.setLocale(defaultLocale);
		return p;
	}
	
	
	
	protected Principal createIdentityRole(String username) {		
		return new YSFPrincipal(username);
	}
	
	@Override public boolean commit() throws LoginException {
		if ( !super.commit() ) 
			return false;
		
		YSFPrincipal prin = (YSFPrincipal)getIdentity();
		prin.setApplication(application);
		prin.setEntity(entityId);
		prin.setCompany(company);
		prin.setEmail(email);
		prin.setDescription(description);
		prin.setRoles(roleSets);
		prin.setLocale(defaultLocale);
		prin.setDatasource(dsJndiName);
		prin.setBigData(bigDataJndiName);
		return true;
	}

	private String getDsJndiName() {
		
		if (this.dsJndiName==null) {
			Configuration c = new Configuration(application);
			this.dsJndiName = c.getString(ConfigurationParameterNames.DATASOURCE);
			this.bigDataJndiName = c.getString(ConfigurationParameterNames.BIG_DATA);
		}
		return this.dsJndiName;
	}
	
	protected PreparedStatement getPS( Connection con ) throws SQLException {
		PreparedStatement ps = con.prepareStatement(principalsQuery +
				" and trd.creationDate<=? and (trd.deadLine> ? or trd.deadLine is null)");
		long l = new java.util.Date().getTime();
        ps.setString(1, myUsername);
        ps.setDate(2, new Date(l));
        ps.setDate(3, new Date(l));
        return ps;
	}
	
		
	   protected String getUsersPassword() throws LoginException {
		   
		   try {
			   
				HttpServletRequest request = (HttpServletRequest) PolicyContext.getContext("javax.servlet.http.HttpServletRequest");
				if (request!=null) {
					UserBean ub = (UserBean)request.getSession().getAttribute(Constants.USER_INFORMATION.getValue());
					if ( excludedEnvironments!=null && ub != null && 
							ub.getEnvironment()!=null && excludedEnvironments.contains(ub.getEnvironment())){
						return  createPasswordHash(ub.getUserCode(), "", "digestCallback");
					}
				}
			} catch (PolicyContextException e) {				
				LogManager.getLog(getClass()).error(e.getMessage(), e);
			}
		   
		   
	      boolean trace = log.isTraceEnabled();
	      String password = null;
	      Connection conn = null;
	      PreparedStatement ps = null;
	      ResultSet rs = null;
	      
	      Transaction tx = null;
	      if (suspendResume)
	      {
	         tx = TransactionDemarcationSupport.suspendAnyTransaction();
	         if (trace)
	            log.trace("suspendAnyTransaction");
	      }

	      try
	      {
	         InitialContext ctx = new InitialContext();
	         DataSource ds = (DataSource) ctx.lookup(getDsJndiName());
	         conn = ds.getConnection();
	         // Get the password
	         if (trace)
	            log.trace("Excuting query: "+principalsQuery+", with username: "+myUsername);
	         ps = getPS(conn);
	         rs = ps.executeQuery();
	         if( rs.next() == false )
	         {
	            if(trace)
	               log.trace("Query returned no matches from db");
	            throw new FailedLoginException("No matching username found in Principals");
	         }
	         
	         password = rs.getString(1);
	         company = rs.getString(2);
	         email = rs.getString(3);
	         description = rs.getString(4);
	         entityId = rs.getString(5);
	         defaultLocale = UserBean.getLocale(rs.getString(6));
	         
	         if(trace)
	            log.trace("Obtained user password");
	      }
	      catch(NamingException ex)
	      {
	         LoginException le = new LoginException("Error looking up DataSource from: "+dsJndiName);
	         le.initCause(ex);
	         throw le;
	      }
	      catch(SQLException ex)
	      {
	         LoginException le = new LoginException("Query failed");
	         le.initCause(ex);
	         throw le;
	      }
	      finally
	      {
	         if (rs != null)
	         {
	            try
	            {
	               rs.close();
	            }
	            catch(SQLException e)
	            {}
	         }
	         if( ps != null )
	         {
	            try
	            {
	               ps.close();
	            }
	            catch(SQLException e)
	            {}
	         }
	         if( conn != null )
	         {
	            try
	            {
	               conn.close();
	            }
	            catch (SQLException ex)
	            {}
	         }
	         if (suspendResume)
	         {
	            TransactionDemarcationSupport.resumeAnyTransaction(tx);
	            if (log.isTraceEnabled())
	               log.trace("resumeAnyTransaction");
	         }
	      }
	      return password;
	   }
	   
	   
	   /** Execute the rolesQuery against the dsJndiName to obtain the roles for
	    the authenticated user.
	     
	    @return Group[] containing the sets of roles
	    */
	   protected Group[] getRoleSets() throws LoginException
	   {
	      
	      if (log.isTraceEnabled())
	         log.trace("getRoleSets using rolesQuery: "+rolesQuery+", username: "+myUsername);
	      roleSets = getRoleSets(myUsername);
	      return roleSets;
	   }
	   
	   /** Execute the rolesQuery against the dsJndiName to obtain the roles for
	    the authenticated user.
	     
	    @return Group[] containing the sets of roles
	    */
	   private Group[] getRoleSets(String username) throws LoginException  {
	      boolean trace = (log!=null) ? log.isTraceEnabled(): false;
	      Connection conn = null;
	      HashMap<String, Group> setsMap = new HashMap<String, Group>();
	      PreparedStatement ps = null;
	      ResultSet rs = null;

	      Transaction tx = null;
	      if (suspendResume)
	      {
	         tx = TransactionDemarcationSupport.suspendAnyTransaction();
	         if( trace )
	            log.trace("suspendAnyTransaction");
	      }

	      try
	      {
	         InitialContext ctx = new InitialContext();
	         DataSource ds = (DataSource) ctx.lookup(getDsJndiName());
	         conn = ds.getConnection();
	         // Get the user role names
	         if (trace)
	            log.trace("Excuting query: "+rolesQuery+", with username: "+myUsername);
	         ps = conn.prepareStatement(rolesQuery);
	         try
	         {
	            ps.setString(1, myUsername);
	         }
	         catch(ArrayIndexOutOfBoundsException ignore)
	         {
	            // The query may not have any parameters so just try it
	         }
	         rs = ps.executeQuery();
	         if( rs.next() == false )
	         {
	            if( trace )
	               log.trace("No roles found");
	            if( getUnauthenticatedIdentity() == null )
	               throw new FailedLoginException("No matching username found in Roles");
	            /* We are running with an unauthenticatedIdentity so create an
	               empty Roles set and return.
	            */
	            Group[] roleSets = { new SimpleGroup("Roles") };
	            return roleSets;
	         }

	         do
	         {
	            String name = rs.getString(1);
	            String groupName = rs.getString(2);
	            if( groupName == null || groupName.length() == 0 )
	               groupName = "Roles";
	            Group group = (Group) setsMap.get(groupName);
	            if( group == null )
	            {
	               group = new SimpleGroup(groupName);
	               setsMap.put(groupName, group);
	            }

	            try
	            {
	               Principal p = createIdentityRole(name);
	               if( trace )
	                  log.trace("Assign user to role " + name);
	               group.addMember(p);
	            }
	            catch(Exception e)
	            {
	               log.debug("Failed to create principal: "+name, e);
	            }
	         } while( rs.next() );
	      }
	      catch(NamingException ex)
	      {
	         LoginException le = new LoginException("Error looking up DataSource from: "+dsJndiName);
	         le.initCause(ex);
	         throw le;
	      }
	      catch(SQLException ex)
	      {
	         LoginException le = new LoginException("Query failed");
	         le.initCause(ex);
	         throw le;
	      }
	      finally
	      {
	         if( rs != null )
	         {
	            try
	            {
	               rs.close();
	            }
	            catch(SQLException e)
	            {}
	         }
	         if( ps != null )
	         {
	            try
	            {
	               ps.close();
	            }
	            catch(SQLException e)
	            {}
	         }
	         if( conn != null )
	         {
	            try
	            {
	               conn.close();
	            }
	            catch (Exception ex)
	            {}
	         }
	         if (suspendResume)
	         {
	            TransactionDemarcationSupport.resumeAnyTransaction(tx);
	            if( trace )
	               log.trace("resumeAnyTransaction");
	         }
	      }
	      
	      Group[] roleSets = new Group[setsMap.size()];
	      setsMap.values().toArray(roleSets);
	      return roleSets;
	   }
	   
	   
//		private String getDsJndiName(YSFPrincipal principal) {
//		Configuration c = new Configuration(principal);
//		return c.getString(ConfigurationParameterNames.DATASOURCE);
//	}
	
	

	   public static Hashtable<String, Set<Principal>> getPrivilegeRole(String dsrc) {
		      Connection conn = null;
		      Hashtable<String, Set<Principal>> resul = new Hashtable<String, Set<Principal>>();
		      Statement ps = null;
		      ResultSet rs = null;

		      Transaction tx = null;
		      
		      tx = TransactionDemarcationSupport.suspendAnyTransaction();
		         
		      

		      try {
		         InitialContext ctx = new InitialContext();
		         DataSource ds = (DataSource) ctx.lookup(dsrc);
		         conn = ds.getConnection();
		         // Get the user role names
		         ps = conn.createStatement();
		         
		         rs = ps.executeQuery("select role, privilege from ysf_jaas_role_privilege");
		         
		         while ( rs.next() ) {
		        	 String priv = rs.getString("privilege");
		        	 String role = rs.getString("role");
		        	 Set<Principal> prinSet = resul.get(priv); 
		        	 if ( prinSet == null ) {
		        		 prinSet = new HashSet<Principal>();
		        		 resul.put(priv, prinSet);
		        	 }
		        	 prinSet.add(new YSFPrincipal(role));
		        	 
		         }
		      } catch(NamingException ex) {
		         
		         
		      } catch(SQLException ex) {
		         
		      } finally {
		         if( rs != null ) {
		            try {
		               rs.close();
		            } catch(SQLException e) {}
		         }
		         if( ps != null ) {
		            try {
		               ps.close();
		            } catch(SQLException e) {}
		         }
		         if( conn != null ) {
		            try {
		               conn.close();
		            } catch (Exception ex) {}
		         }
	            TransactionDemarcationSupport.resumeAnyTransaction(tx);
		      }
		      
		      return resul;
		   }
	  
	  public static String encrypt(String pwd) {
  	    MessageDigest md = null;

  	    try {
  	      md = MessageDigest.getInstance("SHA");
  	      md.update(pwd.getBytes("UTF-8"));
  	      byte raw[] = md.digest(); 
  	      return new String(Base64.encodeBase64(raw));
  	    } catch(NoSuchAlgorithmException e) {
  	      return "";
  	    } catch (UnsupportedEncodingException e) {
  	    	return "";
		}
  	  }
	  
	  
/*	  public static void flushCredentials(String user, String domain) {
	  ObjectName jaasMgr = null;
	  try {
		  jaasMgr = new ObjectName("jboss.security:service=JaasSecurityManager");
	  } catch (MalformedObjectNameException e) {
		LogManager.getLog(getClass()).error(e.getMessage(), e);
	  }
	  Object[] params = {domain, new SimplePrincipal(user)};
	  String[] signature = {"java.lang.String", Principal.class.getName()};
//	  Object[] params = {domain};
//	  String[] signature = {"java.lang.String"};
	  MBeanServer server = (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);
	  try {
		  if ( jaasMgr != null )
			  server.invoke(jaasMgr, "flushAuthenticationCache", params, signature);
		} catch (InstanceNotFoundException e) {
			LogManager.getLog(getClass()).error(e.getMessage(), e);
		} catch (MBeanException e) {
			LogManager.getLog(getClass()).error(e.getMessage(), e);
		} catch (ReflectionException e) {
			LogManager.getLog(getClass()).error(e.getMessage(), e);
		}
	  }
    */
}